{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Low-Level TensorFlow API"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook you will learn how to use TensorFlow's low-level API, then use it to build custom loss functions, as well as custom Keras layers and models."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import os\n",
    "import pandas as pd\n",
    "import sklearn\n",
    "import sys\n",
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "python 3.6.8 (default, Dec 30 2018, 13:01:55) \n",
      "[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)]\n",
      "matplotlib 3.0.2\n",
      "numpy 1.16.1\n",
      "pandas 0.23.4\n",
      "sklearn 0.20.2\n",
      "tensorflow 2.0.0-dev20190304\n",
      "tensorflow.python.keras.api._v2.keras 2.2.4-tf\n"
     ]
    }
   ],
   "source": [
    "print(\"python\", sys.version)\n",
    "for module in mpl, np, pd, sklearn, tf, keras:\n",
    "    print(module.__name__, module.__version__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert sys.version_info >= (3, 5) # Python ≥3.5 required\n",
    "assert tf.__version__ >= \"2.0\"    # TensorFlow ≥2.0 required"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tensors and operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can browse through the code examples or jump directly to the exercises."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tensors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=0, shape=(2, 3), dtype=float32, numpy=\n",
       "array([[1., 2., 3.],\n",
       "       [4., 5., 6.]], dtype=float32)>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tf.constant([[1., 2., 3.], [4., 5., 6.]])\n",
    "t"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "TensorShape([2, 3])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tf.float32"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.dtype"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Indexing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=5, shape=(2, 2), dtype=float32, numpy=\n",
       "array([[2., 3.],\n",
       "       [5., 6.]], dtype=float32)>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t[:, 1:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=10, shape=(2, 1), dtype=float32, numpy=\n",
       "array([[2.],\n",
       "       [5.]], dtype=float32)>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t[..., 1, tf.newaxis]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ops"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=13, shape=(2, 3), dtype=float32, numpy=\n",
       "array([[11., 12., 13.],\n",
       "       [14., 15., 16.]], dtype=float32)>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t + 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=15, shape=(2, 3), dtype=float32, numpy=\n",
       "array([[ 1.,  4.,  9.],\n",
       "       [16., 25., 36.]], dtype=float32)>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.square(t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=19, shape=(2, 2), dtype=float32, numpy=\n",
       "array([[14., 32.],\n",
       "       [32., 77.]], dtype=float32)>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t @ tf.transpose(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### To/From NumPy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1., 2., 3.],\n",
       "       [4., 5., 6.]], dtype=float32)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=22, shape=(2, 3), dtype=float64, numpy=\n",
       "array([[1., 2., 3.],\n",
       "       [4., 5., 6.]])>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = np.array([[1., 2., 3.], [4., 5., 6.]])\n",
    "tf.constant(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.,  4.,  9.],\n",
       "       [16., 25., 36.]], dtype=float32)"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tf.constant([[1., 2., 3.], [4., 5., 6.]])\n",
    "np.square(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Scalars"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=26, shape=(), dtype=float32, numpy=2.718>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tf.constant(2.718)\n",
    "t"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "TensorShape([])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2.718"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.numpy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Conflicting Types"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cannot compute Add as input #1(zero-based) was expected to be a int32 tensor but is a float tensor [Op:Add] name: add/\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    tf.constant(1) + tf.constant(1.0)\n",
    "except tf.errors.InvalidArgumentError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cannot compute Add as input #1(zero-based) was expected to be a double tensor but is a float tensor [Op:Add] name: add/\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    tf.constant(1.0, dtype=tf.float64) + tf.constant(1.0)\n",
    "except tf.errors.InvalidArgumentError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=36, shape=(), dtype=float32, numpy=2.0>"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tf.constant(1.0, dtype=tf.float64)\n",
    "tf.cast(t, tf.float32) + tf.constant(1.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Strings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=38, shape=(), dtype=string, numpy=b'caf\\xc3\\xa9'>"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tf.constant(\"café\")\n",
    "t"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=40, shape=(), dtype=int32, numpy=5>"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.strings.length(t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=42, shape=(), dtype=int32, numpy=4>"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.strings.length(t, unit=\"UTF8_CHAR\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=47, shape=(4,), dtype=int32, numpy=array([ 99,  97, 102, 233], dtype=int32)>"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.strings.unicode_decode(t, \"UTF8\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### String arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = tf.constant([\"Café\", \"Coffee\", \"caffè\", \"咖啡\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=50, shape=(4,), dtype=int32, numpy=array([4, 6, 5, 2], dtype=int32)>"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.strings.length(t, unit=\"UTF8_CHAR\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tf.RaggedTensor(values=tf.Tensor(\n",
       "[   67    97   102   233    67   111   102   102   101   101    99    97\n",
       "   102   102   232 21654 21857], shape=(17,), dtype=int32), row_splits=tf.Tensor([ 0  4 10 15 17], shape=(5,), dtype=int64))"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r = tf.strings.unicode_decode(t, \"UTF8\")\n",
    "r"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ragged tensors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tf.RaggedTensor(values=tf.Tensor([11 12 21 22 23 41], shape=(6,), dtype=int32), row_splits=tf.Tensor([0 2 5 5 6], shape=(5,), dtype=int64))"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r = tf.ragged.constant([[11, 12], [21, 22, 23], [], [41]])\n",
    "r"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<tf.RaggedTensor [[11, 12], [21, 22, 23], [], [41]]>\n"
     ]
    }
   ],
   "source": [
    "print(r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tf.Tensor([21 22 23], shape=(3,), dtype=int32)\n"
     ]
    }
   ],
   "source": [
    "print(r[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<tf.RaggedTensor [[21, 22, 23]]>\n"
     ]
    }
   ],
   "source": [
    "print(r[1:2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<tf.RaggedTensor [[11, 12], [21, 22, 23], [], [41], [51, 52], [], [71]]>\n"
     ]
    }
   ],
   "source": [
    "r2 = tf.ragged.constant([[51, 52], [], [71]])\n",
    "print(tf.concat([r, r2], axis=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<tf.RaggedTensor [[11, 12, 13, 14, 15], [21, 22, 23, 24], [], [41, 42, 43]]>\n"
     ]
    }
   ],
   "source": [
    "r3 = tf.ragged.constant([[13, 14, 15], [24], [], [42, 43]])\n",
    "print(tf.concat([r, r3], axis=1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=281, shape=(4, 3), dtype=int32, numpy=\n",
       "array([[11, 12,  0],\n",
       "       [21, 22, 23],\n",
       "       [ 0,  0,  0],\n",
       "       [41,  0,  0]], dtype=int32)>"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r.to_tensor()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sparse tensors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SparseTensor(indices=tf.Tensor(\n",
      "[[0 1]\n",
      " [1 0]\n",
      " [2 3]], shape=(3, 2), dtype=int64), values=tf.Tensor([1. 2. 3.], shape=(3,), dtype=float32), dense_shape=tf.Tensor([3 4], shape=(2,), dtype=int64))\n"
     ]
    }
   ],
   "source": [
    "s = tf.SparseTensor(indices=[[0, 1], [1, 0], [2, 3]],\n",
    "                    values=[1., 2., 3.],\n",
    "                    dense_shape=[3, 4])\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=290, shape=(3, 4), dtype=float32, numpy=\n",
       "array([[0., 1., 0., 0.],\n",
       "       [2., 0., 0., 0.],\n",
       "       [0., 0., 0., 3.]], dtype=float32)>"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.sparse.to_dense(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "s2 = s * 2.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "unsupported operand type(s) for +: 'SparseTensor' and 'float'\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    s3 = s + 1.\n",
    "except TypeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=295, shape=(3, 2), dtype=float32, numpy=\n",
       "array([[ 30.,  40.],\n",
       "       [ 20.,  40.],\n",
       "       [210., 240.]], dtype=float32)>"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s4 = tf.constant([[10., 20.], [30., 40.], [50., 60.], [70., 80.]])\n",
    "tf.sparse.sparse_dense_matmul(s, s4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SparseTensor(indices=tf.Tensor(\n",
      "[[0 2]\n",
      " [0 1]], shape=(2, 2), dtype=int64), values=tf.Tensor([1. 2.], shape=(2,), dtype=float32), dense_shape=tf.Tensor([3 4], shape=(2,), dtype=int64))\n"
     ]
    }
   ],
   "source": [
    "s5 = tf.SparseTensor(indices=[[0, 2], [0, 1]],\n",
    "                     values=[1., 2.],\n",
    "                     dense_shape=[3, 4])\n",
    "print(s5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "indices[1] = [0,1] is out of order [Op:SparseToDense]\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    tf.sparse.to_dense(s5)\n",
    "except tf.errors.InvalidArgumentError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=310, shape=(3, 4), dtype=float32, numpy=\n",
       "array([[0., 2., 1., 0.],\n",
       "       [0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0.]], dtype=float32)>"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s6 = tf.sparse.reorder(s5)\n",
    "tf.sparse.to_dense(s6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=\n",
       "array([[1., 2., 3.],\n",
       "       [4., 5., 6.]], dtype=float32)>"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v = tf.Variable([[1., 2., 3.], [4., 5., 6.]])\n",
    "v"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=322, shape=(2, 3), dtype=float32, numpy=\n",
       "array([[1., 2., 3.],\n",
       "       [4., 5., 6.]], dtype=float32)>"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v.value()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1., 2., 3.],\n",
       "       [4., 5., 6.]], dtype=float32)"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v.numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=\n",
       "array([[ 2.,  4.,  6.],\n",
       "       [ 8., 10., 12.]], dtype=float32)>"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v.assign(2 * v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=\n",
       "array([[ 2., 42.,  6.],\n",
       "       [ 8., 10., 12.]], dtype=float32)>"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v[0, 1].assign(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=\n",
       "array([[ 2., 42.,  6.],\n",
       "       [ 7.,  8.,  9.]], dtype=float32)>"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v[1].assign([7., 8., 9.])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "'ResourceVariable' object does not support item assignment\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    v[1] = [7., 8., 9.]\n",
    "except TypeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=\n",
       "array([[4., 5., 6.],\n",
       "       [1., 2., 3.]], dtype=float32)>"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sparse_delta = tf.IndexedSlices(values=[[1., 2., 3.], [4., 5., 6.]],\n",
    "                                indices=[1, 0])\n",
    "v.scatter_update(sparse_delta)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=\n",
       "array([[100.,   5.,   6.],\n",
       "       [  1.,   2., 200.]], dtype=float32)>"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v.scatter_nd_update(indices=[[0, 0], [1, 2]],\n",
    "                    updates=[100., 200.])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Devices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.device(\"/cpu:0\"):\n",
    "    t = tf.constant([[1., 2., 3.], [4., 5., 6.]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'/job:localhost/replica:0/task:0/device:CPU:0'"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.device"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "if tf.test.is_gpu_available():\n",
    "    with tf.device(\"/gpu:0\"):\n",
    "        t2 = tf.constant([[1., 2., 3.], [4., 5., 6.]])\n",
    "    print(t2.device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise](https://c1.staticflickr.com/9/8101/8553474140_c50cf08708_b.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 1 – Custom loss function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's start by loading and preparing the California housing dataset. We first load it, then split it into a training set, a validation set and a test set, and finally we scale it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "X_train_full, X_test, y_train_full, y_test = train_test_split(\n",
    "    housing.data, housing.target.reshape(-1, 1), random_state=42)\n",
    "X_train, X_valid, y_train, y_valid = train_test_split(\n",
    "    X_train_full, y_train_full, random_state=42)\n",
    "\n",
    "scaler = StandardScaler()\n",
    "X_train_scaled = scaler.fit_transform(X_train)\n",
    "X_valid_scaled = scaler.transform(X_valid)\n",
    "X_test_scaled = scaler.transform(X_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1)\n",
    "Create an `my_mse()` function with two arguments: the true labels `y_true` and the model predictions `y_pred`. Make it return the mean squared error using TensorFlow operations. Note that you could write your own custom metrics in exactly the same way. **Tip**: recall that the MSE is the mean of the squares of prediction errors, which are the differences between the predictions and the labels, so you will need to use `tf.reduce_mean()` and `tf.square()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2)\n",
    "Compile the following model, passing it your custom loss function, then train it and evaluate it. **Tip**: don't forget to use the scaled sets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1),\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3)\n",
    "Try building and compiling the model again, this time adding `\"mse\"` (or equivalently `\"mean_squared_error\"` or `keras.losses.mean_squared_error`) to the list of additional metrics, then train the model and make sure the `my_mse` is equal to the standard `mse`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4)\n",
    "If you want your code to be portable to other Python implementations of the Keras API, you should use the operations in `keras.backend` rather than TensorFlow operations directly. This package contains thin wrappers around the backend's operations (for example, `keras.backend.square()` simply calls `tf.square()`). Try reimplementing the `my_mse()` function this way and use it to train and evaluate your model again. **Tip**: people frequently define `K = keras.backend` to make their code more readable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise solution](https://camo.githubusercontent.com/250388fde3fac9135ead9471733ee28e049f7a37/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f302f30362f46696c6f735f736567756e646f5f6c6f676f5f253238666c69707065642532392e6a7067)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1)\n",
    "Create an `my_mse()` function with two arguments: the true labels `y_true` and the model predictions `y_pred`. Make it return the mean squared error using TensorFlow operations. Note that you could write your own custom metrics in exactly the same way. **Tip**: recall that the MSE is the mean of the squares of prediction errors, which are the differences between the predictions and the labels, so you will need to use `tf.reduce_mean()` and `tf.square()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "def my_mse(y_true, y_pred):\n",
    "    return tf.reduce_mean(tf.square(y_pred - y_true))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2)\n",
    "Compile your model, passing it your custom loss function, then train it and evaluate it. **Tip**: don't forget to use the scaled sets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1),\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=my_mse, optimizer=\"sgd\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 11610 samples, validate on 3870 samples\n",
      "Epoch 1/10\n",
      "11610/11610 [==============================] - 0s 39us/sample - loss: 2.3013 - val_loss: 1.3952\n",
      "Epoch 2/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.8092 - val_loss: 0.6777\n",
      "Epoch 3/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.6132 - val_loss: 0.5966\n",
      "Epoch 4/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.5610 - val_loss: 0.5658\n",
      "Epoch 5/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.5304 - val_loss: 0.5134\n",
      "Epoch 6/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.5088 - val_loss: 0.4962\n",
      "Epoch 7/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.4929 - val_loss: 0.5083\n",
      "Epoch 8/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.4804 - val_loss: 0.4826\n",
      "Epoch 9/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.4704 - val_loss: 0.4828\n",
      "Epoch 10/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.4622 - val_loss: 0.4846\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x13e9ed0b8>"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.fit(X_train_scaled, y_train, epochs=10,\n",
    "          validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5160/5160 [==============================] - 0s 14us/sample - loss: 0.4569\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0.45686613663222436"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3)\n",
    "Try building and compiling the model again, this time adding `\"mse\"` (or equivalently `\"mean_squared_error\"` or `keras.losses.mean_squared_error`) to the list of additional metrics, then train the model and make sure the `my_mse` is equal to the standard `mse`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 11610 samples, validate on 3870 samples\n",
      "Epoch 1/10\n",
      "11610/11610 [==============================] - 0s 41us/sample - loss: 2.6145 - mean_squared_error: 2.6145 - val_loss: 2.9651 - val_mean_squared_error: 2.9651\n",
      "Epoch 2/10\n",
      "11610/11610 [==============================] - 0s 35us/sample - loss: 0.8185 - mean_squared_error: 0.8185 - val_loss: 1.0717 - val_mean_squared_error: 1.0717\n",
      "Epoch 3/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.7278 - mean_squared_error: 0.7278 - val_loss: 0.6705 - val_mean_squared_error: 0.6705\n",
      "Epoch 4/10\n",
      "11610/11610 [==============================] - 0s 35us/sample - loss: 0.6809 - mean_squared_error: 0.6809 - val_loss: 0.7267 - val_mean_squared_error: 0.7267\n",
      "Epoch 5/10\n",
      "11610/11610 [==============================] - 0s 36us/sample - loss: 0.6535 - mean_squared_error: 0.6535 - val_loss: 0.6830 - val_mean_squared_error: 0.6830\n",
      "Epoch 6/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.6305 - mean_squared_error: 0.6305 - val_loss: 0.5852 - val_mean_squared_error: 0.5852\n",
      "Epoch 7/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.6055 - mean_squared_error: 0.6055 - val_loss: 0.6538 - val_mean_squared_error: 0.6538\n",
      "Epoch 8/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.5914 - mean_squared_error: 0.5914 - val_loss: 0.5965 - val_mean_squared_error: 0.5965\n",
      "Epoch 9/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.5731 - mean_squared_error: 0.5731 - val_loss: 0.6132 - val_mean_squared_error: 0.6132\n",
      "Epoch 10/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.5579 - mean_squared_error: 0.5579 - val_loss: 0.5244 - val_mean_squared_error: 0.5244\n",
      "5160/5160 [==============================] - 0s 14us/sample - loss: 0.5441 - mean_squared_error: 0.5441\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.5441172193187152, 0.5441173]"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1),\n",
    "])\n",
    "model.compile(loss=my_mse, optimizer=\"sgd\", metrics=[\"mean_squared_error\"])\n",
    "model.fit(X_train_scaled, y_train, epochs=10,\n",
    "          validation_data=(X_valid_scaled, y_valid))\n",
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4)\n",
    "If you want your code to be portable to other Python implementations of the Keras API, you should use the operations in `keras.backend` rather than TensorFlow operations directly. This package contains thin wrappers around the backend's operations (for example, `keras.backend.square()` simply calls `tf.square()`). Try reimplementing the `my_mse()` function this way and use it to train and evaluate your model again. **Tip**: people frequently define `K = keras.backend` to make their code more readable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "def my_portable_mse(y_true, y_pred):\n",
    "    K = keras.backend\n",
    "    return K.mean(K.square(y_pred - y_true))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 11610 samples, validate on 3870 samples\n",
      "Epoch 1/10\n",
      "11610/11610 [==============================] - 0s 42us/sample - loss: 1.9864 - mean_squared_error: 1.9864 - val_loss: 2.9839 - val_mean_squared_error: 2.9839\n",
      "Epoch 2/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.7440 - mean_squared_error: 0.7440 - val_loss: 0.7090 - val_mean_squared_error: 0.7090\n",
      "Epoch 3/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.6614 - mean_squared_error: 0.6614 - val_loss: 0.6152 - val_mean_squared_error: 0.6152\n",
      "Epoch 4/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.6243 - mean_squared_error: 0.6243 - val_loss: 0.5762 - val_mean_squared_error: 0.5762\n",
      "Epoch 5/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.5936 - mean_squared_error: 0.5936 - val_loss: 0.5499 - val_mean_squared_error: 0.5499\n",
      "Epoch 6/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.5677 - mean_squared_error: 0.5677 - val_loss: 0.5243 - val_mean_squared_error: 0.5243\n",
      "Epoch 7/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.5455 - mean_squared_error: 0.5455 - val_loss: 0.5017 - val_mean_squared_error: 0.5017\n",
      "Epoch 8/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.5265 - mean_squared_error: 0.5265 - val_loss: 0.4844 - val_mean_squared_error: 0.4844\n",
      "Epoch 9/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.5100 - mean_squared_error: 0.5100 - val_loss: 0.4692 - val_mean_squared_error: 0.4692\n",
      "Epoch 10/10\n",
      "11610/11610 [==============================] - 0s 33us/sample - loss: 0.4963 - mean_squared_error: 0.4963 - val_loss: 0.4572 - val_mean_squared_error: 0.4572\n",
      "5160/5160 [==============================] - 0s 15us/sample - loss: 0.4914 - mean_squared_error: 0.4914\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.4914473322472831, 0.49144742]"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1),\n",
    "])\n",
    "model.compile(loss=my_portable_mse, optimizer=\"sgd\", metrics=[\"mean_squared_error\"])\n",
    "model.fit(X_train_scaled, y_train, epochs=10,\n",
    "          validation_data=(X_valid_scaled, y_valid))\n",
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise](https://c1.staticflickr.com/9/8101/8553474140_c50cf08708_b.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 2 – Custom layer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1)\n",
    "Some layers have no weights, such as `keras.layers.Flatten` or `keras.layers.ReLU`. If you want to create a custom layer without any weights, the simplest option is to create a `keras.layers.Lambda` layer and pass it the function to perform. For example, try creating a custom layer that applies the softplus function (log(exp(X) + 1), and try calling this layer like a regular function.\n",
    "\n",
    "**Tip**: you can use `tf.math.softplus()` rather than computing the log and the exponential manually."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2)\n",
    "Create a regression model like in exercise 1, but add your softplus layer at the top (i.e., after the existing 1-unit dense layer). This can be useful to ensure that your model never predicts negative values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3)\n",
    "Alternatively, try using this softplus layer as the activation function of the output layer.\n",
    "\n",
    "**Notes**:\n",
    "* setting a layer's activation function is just a handy way of adding an extra weightless layer.\n",
    "* Keras supports the softplus activation function out of the box:\n",
    "  * set `activation=\"softplus\"`\n",
    "  * or set `activation=keras.activations.softplus`\n",
    "  * or add a `keras.layers.Activation(\"softplus\")` layer to your model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4)\n",
    "Now let's create a custom layer with its own weights. Use the following template to create a `MyDense` layer that computes $\\phi(\\mathbf{X} \\mathbf{W}) + \\mathbf{b}$, where $\\phi$ is the (optional) activation function, $\\mathbf{X}$ is the input data, $\\mathbf{W}$ represents the kernel (i.e., connection weights), and $\\mathbf{b}$ represents the biases, then train and evaluate a model using this instead of a regular `Dense` layer.\n",
    "\n",
    "**Tips**:\n",
    "* The constructor `__init__()`:\n",
    "  * It must have all your layer's hyperparameters as arguments, and save them to instance variables. You will need the number of `units` and the optional `activation` function. To support all kinds of activation functions (strings or functions), simply create a `keras.layers.Activation` passing it the `activation` argument.\n",
    "  * The `**kwargs` argument must be passed to the base class's constructor (`super().__init__()`) so your class can support the `input_shape` argument, and more.\n",
    "* The `build()` method:\n",
    "  * The `build()` method will be called automatically by Keras when it knows the shape of the inputs. Note that the argument should really be called `batch_input_shape` since it includes the batch size.\n",
    "  * You must call `self.add_weight()` for each weight you want to create, specifying its `name`, `shape` (which often depends on the `input_shape`), how to initialize it, and whether or not it is `trainable`. You need two weights: the `kernel` (connection weights) and the `biases`. The kernel must be initialized randomly. The biases are usually initialized with zeros. **Note**: you can find many initializers in `keras.initializers`.\n",
    "  * Do not forget to call `super().build()`, so Keras knows that the model has been built.\n",
    "  * Note: you could create the weights in the constructor, but it is preferable to create them in the `build()` method, because users of your class may not always know the `input_shape` when creating the model. The first time the model is used on some actual data, the `build()` method will automatically be called with the actual `input_shape`.\n",
    "* The `call()` method:\n",
    "  * This is where to code your layer's actual computations. As before, you can use TensorFlow operations directly, or use `keras.backend` operations if you want the layer to be portable to other Keras implementations.\n",
    "* The `compute_output_shape()` method:\n",
    "  * You do not need to implement this method when using tf.keras, as the `Layer` class provides a good implementation.\n",
    "  * However, if want to port your code to another Keras implementation (such as keras-team), and if the output shape is different from the input shape, then you need to implement this method. Note that the input shape is actually the batch input shape, and the ouptut shape must be the batch output shape."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This template was copied from https://keras.io/layers/writing-your-own-keras-layers/\n",
    "# I just removed the imports and replaced Layer with keras.layers.Layer.\n",
    "\n",
    "class MyLayer(keras.layers.Layer):\n",
    "    def __init__(self, output_dim, **kwargs):\n",
    "        self.output_dim = output_dim\n",
    "        super(MyLayer, self).__init__(**kwargs)\n",
    "\n",
    "    def build(self, input_shape):\n",
    "        # Create a trainable weight variable for this layer.\n",
    "        self.kernel = self.add_weight(name='kernel', \n",
    "                                      shape=(input_shape[1], self.output_dim),\n",
    "                                      initializer='uniform',\n",
    "                                      trainable=True)\n",
    "        super(MyLayer, self).build(input_shape)  # Be sure to call this at the end\n",
    "\n",
    "    def call(self, x):\n",
    "        return K.dot(x, self.kernel)\n",
    "\n",
    "    def compute_output_shape(self, input_shape):\n",
    "        return (input_shape[0], self.output_dim)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise solution](https://camo.githubusercontent.com/250388fde3fac9135ead9471733ee28e049f7a37/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f302f30362f46696c6f735f736567756e646f5f6c6f676f5f253238666c69707065642532392e6a7067)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 2 – Solution"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1)\n",
    "Some layers have no weights, such as `keras.layers.Flatten` or `keras.layers.ReLU`. If you want to create a custom layer without any weights, the simplest option is to create a `keras.layers.Lambda` layer and pass it the function to perform. For example, try creating a custom layer that applies the softplus function (log(exp(X) + 1), and try calling this layer like a regular function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "my_softplus = keras.layers.Lambda(lambda X: tf.nn.softplus(X))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=107512, shape=(5,), dtype=float32, numpy=\n",
       "array([4.5417706e-05, 6.7153489e-03, 6.9314718e-01, 5.0067153e+00,\n",
       "       1.0000046e+01], dtype=float32)>"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_softplus([-10., -5., 0., 5., 10.])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2)\n",
    "Create a regression model like in exercise 1, but add your softplus layer at the top (i.e., after the existing 1-unit dense layer). This can be useful to ensure that your model never predicts negative values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 11610 samples, validate on 3870 samples\n",
      "Epoch 1/10\n",
      "11610/11610 [==============================] - 0s 38us/sample - loss: 2.1855 - val_loss: 1.6445\n",
      "Epoch 2/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 1.0730 - val_loss: 0.8504\n",
      "Epoch 3/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.8289 - val_loss: 0.7364\n",
      "Epoch 4/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.7531 - val_loss: 0.6832\n",
      "Epoch 5/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.7082 - val_loss: 0.6478\n",
      "Epoch 6/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.6745 - val_loss: 0.6196\n",
      "Epoch 7/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.6468 - val_loss: 0.5953\n",
      "Epoch 8/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.6221 - val_loss: 0.5733\n",
      "Epoch 9/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.5997 - val_loss: 0.5536\n",
      "Epoch 10/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.5795 - val_loss: 0.5349\n",
      "5160/5160 [==============================] - 0s 14us/sample - loss: 0.5649\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0.564937449148459"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1),\n",
    "    my_softplus\n",
    "])\n",
    "model.compile(loss=my_portable_mse, optimizer=\"sgd\")\n",
    "model.fit(X_train_scaled, y_train, epochs=10,\n",
    "          validation_data=(X_valid_scaled, y_valid))\n",
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3)\n",
    "Alternatively, try using this softplus layer as the activation function of the output layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 11610 samples, validate on 3870 samples\n",
      "Epoch 1/10\n",
      "11610/11610 [==============================] - 0s 38us/sample - loss: 1.9194 - val_loss: 1.8773\n",
      "Epoch 2/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.8180 - val_loss: 0.7519\n",
      "Epoch 3/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.6807 - val_loss: 0.6417\n",
      "Epoch 4/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.6313 - val_loss: 0.6009\n",
      "Epoch 5/10\n",
      "11610/11610 [==============================] - 0s 31us/sample - loss: 0.5954 - val_loss: 0.5693\n",
      "Epoch 6/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.5669 - val_loss: 0.5437\n",
      "Epoch 7/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.5440 - val_loss: 0.5220\n",
      "Epoch 8/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.5247 - val_loss: 0.5045\n",
      "Epoch 9/10\n",
      "11610/11610 [==============================] - 0s 32us/sample - loss: 0.5087 - val_loss: 0.4885\n",
      "Epoch 10/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.4952 - val_loss: 0.4744\n",
      "5160/5160 [==============================] - 0s 14us/sample - loss: 0.4768\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0.47681303236835687"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1, activation=my_softplus)\n",
    "#   A few alternatives...\n",
    "#   keras.layers.Dense(1, activation=tf.function(lambda X: my_softplus(X)))\n",
    "#   keras.layers.Dense(1, activation=\"softplus\")\n",
    "#   keras.layers.Dense(1, activation=keras.activations.softplus)\n",
    "#   keras.layers.Dense(1), keras.layers.Activation(\"softplus\")\n",
    "])\n",
    "\n",
    "model.compile(loss=my_portable_mse, optimizer=\"sgd\")\n",
    "model.fit(X_train_scaled, y_train, epochs=10,\n",
    "          validation_data=(X_valid_scaled, y_valid))\n",
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4)\n",
    "Now let's create a custom layer with its own weights. Use the following template to create a `MyDense` layer that computes $\\phi(\\mathbf{X} \\mathbf{W}) + \\mathbf{b}$, where $\\phi$ is the (optional) activation function, $\\mathbf{X}$ is the input data, $\\mathbf{W}$ represents the kernel (i.e., connection weights), and $\\mathbf{b}$ represents the biases, then train and evaluate a model using this instead of a regular `Dense` layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyDense(keras.layers.Layer):\n",
    "    def __init__(self, units, activation=None, **kwargs):\n",
    "        self.units = units\n",
    "        self.activation = keras.layers.Activation(activation)\n",
    "        super(MyDense, self).__init__(**kwargs)\n",
    "\n",
    "    def build(self, input_shape):\n",
    "        self.kernel = self.add_weight(name='kernel', \n",
    "                                      shape=(input_shape[1], self.units),\n",
    "                                      initializer='uniform',\n",
    "                                      trainable=True)\n",
    "        self.biases = self.add_weight(name='bias', \n",
    "                                      shape=(self.units,),\n",
    "                                      initializer='zeros',\n",
    "                                      trainable=True)\n",
    "        super(MyDense, self).build(input_shape)\n",
    "\n",
    "    def call(self, X):\n",
    "        return self.activation(X @ self.kernel + self.biases)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential([\n",
    "    MyDense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    MyDense(1)\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 11610 samples, validate on 3870 samples\n",
      "Epoch 1/10\n",
      "11610/11610 [==============================] - 1s 48us/sample - loss: 3.5886 - val_loss: 2.1575\n",
      "Epoch 2/10\n",
      "11610/11610 [==============================] - 0s 35us/sample - loss: 1.7396 - val_loss: 1.3169\n",
      "Epoch 3/10\n",
      "11610/11610 [==============================] - 0s 35us/sample - loss: 1.1695 - val_loss: 0.9721\n",
      "Epoch 4/10\n",
      "11610/11610 [==============================] - 0s 35us/sample - loss: 0.8931 - val_loss: 0.7806\n",
      "Epoch 5/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.7530 - val_loss: 0.6845\n",
      "Epoch 6/10\n",
      "11610/11610 [==============================] - 0s 35us/sample - loss: 0.6859 - val_loss: 0.6331\n",
      "Epoch 7/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.6483 - val_loss: 0.6010\n",
      "Epoch 8/10\n",
      "11610/11610 [==============================] - 0s 35us/sample - loss: 0.6222 - val_loss: 0.5775\n",
      "Epoch 9/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.6013 - val_loss: 0.5575\n",
      "Epoch 10/10\n",
      "11610/11610 [==============================] - 0s 34us/sample - loss: 0.5828 - val_loss: 0.5397\n",
      "5160/5160 [==============================] - 0s 16us/sample - loss: 0.5635\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0.5634679314702056"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.compile(loss=\"mse\", optimizer=\"sgd\")\n",
    "model.fit(X_train_scaled, y_train, epochs=10,\n",
    "          validation_data=(X_valid_scaled, y_valid))\n",
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise](https://c1.staticflickr.com/9/8101/8553474140_c50cf08708_b.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 3 – TensorFlow Functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1)\n",
    "Examine and run the following code examples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [],
   "source": [
    "def scaled_elu(z, scale=1.0, alpha=1.0):\n",
    "    is_positive = tf.greater_equal(z, 0.0)\n",
    "    return scale * tf.where(is_positive, z, alpha * tf.nn.elu(z))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=194619, shape=(), dtype=float32, numpy=-0.95021296>"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scaled_elu(tf.constant(-3.))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=194629, shape=(2,), dtype=float32, numpy=array([-0.95021296,  2.5       ], dtype=float32)>"
      ]
     },
     "execution_count": 76,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scaled_elu(tf.constant([-3., 2.5]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.eager.def_function.Function at 0x13f60d9b0>"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scaled_elu_tf = tf.function(scaled_elu)\n",
    "scaled_elu_tf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=194646, shape=(), dtype=float32, numpy=-0.95021296>"
      ]
     },
     "execution_count": 78,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scaled_elu_tf(tf.constant(-3.))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=194662, shape=(2,), dtype=float32, numpy=array([-0.95021296,  2.5       ], dtype=float32)>"
      ]
     },
     "execution_count": 79,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scaled_elu_tf(tf.constant([-3., 2.5]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scaled_elu_tf.python_function is scaled_elu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8.18 ms ± 161 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit scaled_elu(tf.random.normal((1000, 1000)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "7.49 ms ± 353 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit scaled_elu_tf(tf.random.normal((1000, 1000)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [],
   "source": [
    "def display_tf_code(func):\n",
    "    from IPython.display import display, Markdown\n",
    "    if hasattr(func, \"python_function\"):\n",
    "        func = func.python_function\n",
    "    code = tf.autograph.to_code(func, experimental_optional_features=None)\n",
    "    display(Markdown('```python\\n{}\\n```'.format(code)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "```python\n",
       "from __future__ import print_function\n",
       "\n",
       "def tf__scaled_elu(z, scale=None, alpha=None):\n",
       "  do_return = False\n",
       "  retval_ = None\n",
       "  is_positive = ag__.converted_call('greater_equal', tf, ag__.ConversionOptions(recursive=True, verbose=0, strip_decorators=(ag__.convert, ag__.do_not_convert, ag__.converted_call), force_conversion=False, optional_features=(), internal_convert_user_code=True), (z, 0.0), {})\n",
       "  do_return = True\n",
       "  retval_ = scale * ag__.converted_call('where', tf, ag__.ConversionOptions(recursive=True, verbose=0, strip_decorators=(ag__.convert, ag__.do_not_convert, ag__.converted_call), force_conversion=False, optional_features=(), internal_convert_user_code=True), (is_positive, z, alpha * tf.nn.elu(z)), {})\n",
       "  return retval_\n",
       "\n",
       "\n",
       "\n",
       "tf__scaled_elu.autograph_info__ = {}\n",
       "\n",
       "```"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_tf_code(scaled_elu)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [],
   "source": [
    "var = tf.Variable(0)\n",
    "\n",
    "@tf.function\n",
    "def add_21():\n",
    "    return var.assign_add(21)\n",
    "\n",
    "@tf.function\n",
    "def times_2():\n",
    "    return var.assign(var * 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=212544, shape=(), dtype=int32, numpy=42>"
      ]
     },
     "execution_count": 86,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add_21()\n",
    "times_2()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [],
   "source": [
    "def times_4(x):\n",
    "    return 4. * x\n",
    "\n",
    "@tf.function\n",
    "def times_4_plus_22(x):\n",
    "    return times_4(x) + 22."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=212557, shape=(), dtype=float32, numpy=42.0>"
      ]
     },
     "execution_count": 88,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "times_4_plus_22(tf.constant(5.))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Compute 1 + 1/2 + 1/4 + ...: the order of execution of the operations with side-effects (e.g., `assign()`) is preserved (in TF 1.x, `tf.control_dependencies()` was needed in such cases):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=212641, shape=(), dtype=float32, numpy=1.9999981>"
      ]
     },
     "execution_count": 89,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "total = tf.Variable(0.)\n",
    "increment = tf.Variable(1.)\n",
    "\n",
    "@tf.function\n",
    "def converge_to_2(n_iterations):\n",
    "    for i in tf.range(n_iterations):\n",
    "        total.assign_add(increment)\n",
    "        increment.assign(increment / 2.0)\n",
    "    return total\n",
    "\n",
    "converge_to_2(20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2)\n",
    "Write a function that computes the sum of squares from 1 to n, where n is an argument. Convert it to a graph function by using `tf.function` as a decorator. Display the code generated by autograph using the `display_tf_code()` function. Use `%timeit` to see how must faster the TensorFlow `Function` is compared to the Python function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3)\n",
    "Examine and run the following code examples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tf.function\n",
    "def square(x):\n",
    "    tf.print(\"Calling\", x)  # part of the TF Function\n",
    "    print(\"Tracing\")  # NOT part of the TF Function\n",
    "    return tf.square(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tracing\n",
      "Calling 0\n",
      "Calling 1\n",
      "Calling 2\n",
      "Calling 3\n",
      "Calling 4\n"
     ]
    }
   ],
   "source": [
    "for i in range(5):\n",
    "    square(tf.constant(i))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tracing\n",
      "Calling 0\n",
      "Calling 1\n",
      "Calling 2\n",
      "Calling 3\n",
      "Calling 4\n"
     ]
    }
   ],
   "source": [
    "for i in range(5):\n",
    "    square(tf.constant(i, dtype=tf.float32))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tracing\n",
      "Calling [0 0]\n",
      "Calling [1 1]\n",
      "Calling [2 2]\n",
      "Calling [3 3]\n",
      "Calling [4 4]\n"
     ]
    }
   ],
   "source": [
    "for i in range(5):\n",
    "    square(tf.constant([i, i], dtype=tf.float32))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tracing\n",
      "Calling 0\n",
      "Tracing\n",
      "Calling 1\n",
      "Tracing\n",
      "Calling 2\n",
      "Tracing\n",
      "Calling 3\n",
      "Tracing\n",
      "Calling 4\n"
     ]
    }
   ],
   "source": [
    "# WARNING: when passing non-tensor values, a trace happens for any new value!\n",
    "# This is to allow optimization in case this value determines e.g., number of layers.\n",
    "for i in range(5):\n",
    "    square(i)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.4)\n",
    "When you give Keras a custom loss function, it actually creates a graph function based on it, and then uses that graph function during training. The same is true of custom metric functions, and the `call()` method of custom layers and models. Create a `my_mse()` function, like you did earlier, but add an instruction to log a message inside it (use `print()`, *not* `tf.print()`!), and verify that the message is only logged once when you compile and train the model. Optionally, you can also find out when Keras converts custom metrics, layers and models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.5)\n",
    "Examine the following function, and try to call it with various argument types and shapes. Notice that only tensors of type `int32` and one dimension (of any size) are accepted now that we have specified the `input_signature`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tf.function(input_signature=[tf.TensorSpec([None], tf.int32, name=\"x\")])\n",
    "def cube(z):\n",
    "    return tf.pow(z, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise solution](https://camo.githubusercontent.com/250388fde3fac9135ead9471733ee28e049f7a37/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f302f30362f46696c6f735f736567756e646f5f6c6f676f5f253238666c69707065642532392e6a7067)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 3 – Solution"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1)\n",
    "Examine the code examples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Done."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2)\n",
    "Write a function that computes the sum of squares from 1 to n, where n is an argument. Convert it to a graph function by using `tf.function` as a decorator. Display the code generated by autograph using the `display_tf_code()` function. Use `%timeit` to see how must faster the TensorFlow `Function` is compared to the Python function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tf.function\n",
    "def sum_squares(n):\n",
    "    s = tf.constant(0)\n",
    "    for i in range(1, n + 1):\n",
    "        s = s + i ** 2\n",
    "    return s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=212810, shape=(), dtype=int32, numpy=55>"
      ]
     },
     "execution_count": 97,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum_squares(tf.constant(5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "```python\n",
       "from __future__ import print_function\n",
       "\n",
       "def tf__sum_squares(n):\n",
       "  do_return = False\n",
       "  retval_ = None\n",
       "  s = ag__.converted_call('constant', tf, ag__.ConversionOptions(recursive=True, verbose=0, strip_decorators=(ag__.convert, ag__.do_not_convert, ag__.converted_call), force_conversion=False, optional_features=(), internal_convert_user_code=True), (0,), {})\n",
       "\n",
       "  def loop_body(loop_vars, s_1):\n",
       "    i = loop_vars\n",
       "    s_1 = s_1 + i ** 2\n",
       "    return s_1,\n",
       "  s, = ag__.for_stmt(ag__.converted_call(range, None, ag__.ConversionOptions(recursive=True, verbose=0, strip_decorators=(ag__.convert, ag__.do_not_convert, ag__.converted_call), force_conversion=False, optional_features=(), internal_convert_user_code=True), (1, n + 1), {}), None, loop_body, (s,))\n",
       "  do_return = True\n",
       "  retval_ = s\n",
       "  return retval_\n",
       "\n",
       "\n",
       "\n",
       "tf__sum_squares.autograph_info__ = {}\n",
       "\n",
       "```"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_tf_code(sum_squares.python_function)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "274 µs ± 90.9 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
     ]
    }
   ],
   "source": [
    "%timeit sum_squares(10000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "358 ms ± 16.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
     ]
    }
   ],
   "source": [
    "%timeit sum_squares.python_function(10000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3)\n",
    "Examine the code examples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Done."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.4)\n",
    "When you give Keras a custom loss function, it actually creates a graph function based on it, and then uses that graph function during training. The same is true of custom metric functions, and the `call()` method of custom layers and models. Create a `my_mse()` function, like you did earlier, but add an instruction to log a message inside it (use `print()`, *not* `tf.print()`!), and verify that the message is only logged once when you compile and train the model. Optionally, you can also find out when Keras converts custom metrics, layers and models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom loss function\n",
    "def my_mse(y_true, y_pred):\n",
    "    print(\"Tracing loss my_mse()\")\n",
    "    return tf.reduce_mean(tf.square(y_pred - y_true))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom metric function\n",
    "def my_mae(y_true, y_pred):\n",
    "    print(\"Tracing metric my_mae()\")\n",
    "    return tf.reduce_mean(tf.abs(y_pred - y_true))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom layer\n",
    "class MyDense(keras.layers.Layer):\n",
    "    def __init__(self, units, activation=None, **kwargs):\n",
    "        self.units = units\n",
    "        self.activation = keras.layers.Activation(activation)\n",
    "        super(MyDense, self).__init__(**kwargs)\n",
    "\n",
    "    def build(self, input_shape):\n",
    "        self.kernel = self.add_weight(name='kernel', \n",
    "                                      shape=(input_shape[1], self.units),\n",
    "                                      initializer='uniform',\n",
    "                                      trainable=True)\n",
    "        self.biases = self.add_weight(name='bias', \n",
    "                                      shape=(self.units,),\n",
    "                                      initializer='zeros',\n",
    "                                      trainable=True)\n",
    "        super(MyDense, self).build(input_shape)\n",
    "\n",
    "    def call(self, X):\n",
    "        print(\"Tracing MyDense.call()\")\n",
    "        return self.activation(X @ self.kernel + self.biases)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom model\n",
    "class MyModel(keras.models.Model):\n",
    "    def __init__(self):\n",
    "        super(MyModel, self).__init__()\n",
    "        self.hidden1 = MyDense(30, activation=\"relu\")\n",
    "        self.hidden2 = MyDense(30, activation=\"relu\")\n",
    "        self.output_ = MyDense(1)\n",
    "\n",
    "    def call(self, input):\n",
    "        print(\"Tracing MyModel.call()\")\n",
    "        hidden1 = self.hidden1(input)\n",
    "        hidden2 = self.hidden2(hidden1)\n",
    "        concat = keras.layers.concatenate([input, hidden2])\n",
    "        output = self.output_(concat)\n",
    "        return output\n",
    "\n",
    "model = MyModel()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=my_mse, optimizer=\"sgd\", metrics=[my_mae])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tracing MyModel.call()\n",
      "Tracing MyDense.call()\n",
      "Tracing MyDense.call()\n",
      "Tracing MyDense.call()\n",
      "Tracing metric my_mae()\n",
      "Tracing metric my_mae()\n",
      "Tracing loss my_mse()\n",
      "Train on 11610 samples, validate on 3870 samples\n",
      "Epoch 1/2\n",
      "11610/11610 [==============================] - 1s 46us/sample - loss: 3.3322 - my_mae: 1.4788 - val_loss: 1.9528 - val_my_mae: 0.9931\n",
      "Epoch 2/2\n",
      "11610/11610 [==============================] - 0s 36us/sample - loss: 1.2325 - my_mae: 0.7870 - val_loss: 1.3645 - val_my_mae: 0.6417\n",
      "5160/5160 [==============================] - 0s 15us/sample - loss: 0.8552 - my_mae: 0.6404\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.8551779761794925, 0.640441]"
      ]
     },
     "execution_count": 106,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.fit(X_train_scaled, y_train, epochs=2,\n",
    "          validation_data=(X_valid_scaled, y_valid))\n",
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that each custom function is traced just once, except for the metric function. That's a bit odd."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.5)\n",
    "Examine the following function, and try to call it with various argument types and shapes. Notice that only tensors of type `int32` and one dimension (of any size) are accepted now that we have specified the `input_signature`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tf.function(input_signature=[tf.TensorSpec([None], tf.int32, name=\"x\")])\n",
    "def cube(z):\n",
    "    return tf.pow(z, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=401992, shape=(3,), dtype=int32, numpy=array([ 1,  8, 27], dtype=int32)>"
      ]
     },
     "execution_count": 108,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube(tf.constant([1, 2, 3]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=401995, shape=(5,), dtype=int32, numpy=array([  1,   8,  27,  64, 125], dtype=int32)>"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube(tf.constant([1, 2, 3, 4, 5]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    cube([1, 2, 3])\n",
    "except ValueError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python inputs incompatible with input_signature: inputs ((<tf.Tensor: id=401999, shape=(3,), dtype=float32, numpy=array([1., 2., 3.], dtype=float32)>,)), input_signature ((TensorSpec(shape=(None,), dtype=tf.int32, name='x'),))\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    cube(tf.constant([1., 2., 3]))\n",
    "except ValueError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python inputs incompatible with input_signature: inputs ((<tf.Tensor: id=402001, shape=(2, 2), dtype=int32, numpy=\n",
      "array([[1, 2],\n",
      "       [3, 4]], dtype=int32)>,)), input_signature ((TensorSpec(shape=(None,), dtype=tf.int32, name='x'),))\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    cube(tf.constant([[1, 2], [3, 4]]))\n",
    "except ValueError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise](https://c1.staticflickr.com/9/8101/8553474140_c50cf08708_b.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 4 – Function Graphs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1)\n",
    "Examine and run the following code examples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tf.function(input_signature=[tf.TensorSpec([None], tf.int32, name=\"x\")])\n",
    "def cube(z):\n",
    "    return tf.pow(z, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.eager.function.ConcreteFunction at 0x13fd37160>"
      ]
     },
     "execution_count": 114,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32 = cube.get_concrete_function(tf.TensorSpec([None], tf.int32))\n",
    "cube_func_int32"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 115,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32 is cube.get_concrete_function(tf.TensorSpec([5], tf.int32))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 116,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32 is cube.get_concrete_function(tf.constant([1, 2, 3]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.framework.func_graph.FuncGraph at 0x14d5ffc88>"
      ]
     },
     "execution_count": 117,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32.graph"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2)\n",
    "The function's graph is represented on the following diagram. Call the graph's `get_operations()` method to get the list of operations. Each operation has an `inputs` attribute that returns an iterator over its input tensors (these are symbolic: contrary to tensors we have used up to now, they have no value). It also has an `outputs` attribute that returns the list of output tensors. Each tensor has an `op` attribute that returns the operation it comes from. Try navigating through the graph using these methods and attributes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"images/cube_graph.png\" width=\"600\" />"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3)\n",
    "Each operation has a default name, such as `\"pow\"` (you can override it by setting the `name` attribute when you call the operation). In case of a name conflict, TensorFlow adds an underscore and anindex to make the name unique (e.g. `\"pow_1\"`). Moreover, each tensor has the same name as the operation that outputs it, followed by a colon `:` and the tensor's `index` (e.g., `\"pow:0\"`). Most operations have a single output tensor, so most tensors have a name that ends with `:0`. Try using `get_operation_by_name()` and `get_tensor_by_name()` to access any op and tensor you wish."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.4)\n",
    "Call the graph's `as_graph_def()` method and print the output. This is a protobuf representation of the computation graph: it is what makes TensorFlow models so portable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.5)\n",
    "Get the concrete function's `function_def`, and look at its `signature`. This shows the names and types of the nodes in the graph that correspond to the function's inputs and outputs. This will come in handy when you deploy models to TensorFlow Serving or Google Cloud ML Engine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise solution](https://camo.githubusercontent.com/250388fde3fac9135ead9471733ee28e049f7a37/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f302f30362f46696c6f735f736567756e646f5f6c6f676f5f253238666c69707065642532392e6a7067)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 4 – Solution"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1)\n",
    "Examine the code examples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Done."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2)\n",
    "The function's graph is represented on the following diagram. Call the graph's `get_operations()` method to get the list of operations. Each operation has an `inputs` attribute that returns an iterator over its input tensors (these are symbolic: contrary to tensors we have used up to now, they have no value). It also has an `outputs` attribute that returns the list of output tensors. Each tensor has an `op` attribute that returns the operation it comes from. Try navigating through the graph using these methods and attributes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"images/cube_graph.png\" width=\"600\" />"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<tf.Operation 'x' type=Placeholder>,\n",
       " <tf.Operation 'Pow/y' type=Const>,\n",
       " <tf.Operation 'Pow' type=Pow>,\n",
       " <tf.Operation 'Identity' type=Identity>]"
      ]
     },
     "execution_count": 118,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32.graph.get_operations()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Operation 'Pow' type=Pow>"
      ]
     },
     "execution_count": 119,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pow_op = cube_func_int32.graph.get_operations()[2]\n",
    "pow_op"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<tf.Tensor 'x:0' shape=(None,) dtype=int32>,\n",
       " <tf.Tensor 'Pow/y:0' shape=() dtype=int32>]"
      ]
     },
     "execution_count": 120,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pow_in = list(pow_op.inputs)\n",
    "pow_in"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 121,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<tf.Tensor 'Pow:0' shape=(None,) dtype=int32>]"
      ]
     },
     "execution_count": 121,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pow_out = list(pow_op.outputs)\n",
    "pow_out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 122,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<tf.Tensor 'x:0' shape=(None,) dtype=int32>,\n",
       " <tf.Tensor 'Pow/y:0' shape=() dtype=int32>]"
      ]
     },
     "execution_count": 122,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pow_in = list(pow_op.inputs)\n",
    "pow_in"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 123,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Operation 'x' type=Placeholder>"
      ]
     },
     "execution_count": 123,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pow_in[0].op"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3)\n",
    "Each operation has a default name, such as `\"pow\"` (you can override it by setting the `name` attribute when you call the operation). In case of a name conflict, TensorFlow adds an underscore and anindex to make the name unique (e.g. `\"pow_1\"`). Moreover, each tensor has the same name as the operation that outputs it, followed by a colon `:` and the tensor's `index` (e.g., `\"pow:0\"`). Most operations have a single output tensor, so most tensors have a name that ends with `:0`. Try using `get_operation_by_name()` and `get_tensor_by_name()` to access any op and tensor you wish."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 124,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Operation 'x' type=Placeholder>"
      ]
     },
     "execution_count": 124,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32.graph.get_operation_by_name(\"x\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'x:0' shape=(None,) dtype=int32>"
      ]
     },
     "execution_count": 125,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32.graph.get_tensor_by_name(\"x:0\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.4)\n",
    "Call the graph's `as_graph_def()` method and print the output. This is a protobuf representation of the computation graph: it is what makes TensorFlow models so portable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "node {\n",
       "  name: \"x\"\n",
       "  op: \"Placeholder\"\n",
       "  attr {\n",
       "    key: \"_user_specified_name\"\n",
       "    value {\n",
       "      s: \"x\"\n",
       "    }\n",
       "  }\n",
       "  attr {\n",
       "    key: \"dtype\"\n",
       "    value {\n",
       "      type: DT_INT32\n",
       "    }\n",
       "  }\n",
       "  attr {\n",
       "    key: \"shape\"\n",
       "    value {\n",
       "      shape {\n",
       "        dim {\n",
       "          size: -1\n",
       "        }\n",
       "      }\n",
       "    }\n",
       "  }\n",
       "}\n",
       "node {\n",
       "  name: \"Pow/y\"\n",
       "  op: \"Const\"\n",
       "  attr {\n",
       "    key: \"dtype\"\n",
       "    value {\n",
       "      type: DT_INT32\n",
       "    }\n",
       "  }\n",
       "  attr {\n",
       "    key: \"value\"\n",
       "    value {\n",
       "      tensor {\n",
       "        dtype: DT_INT32\n",
       "        tensor_shape {\n",
       "        }\n",
       "        int_val: 3\n",
       "      }\n",
       "    }\n",
       "  }\n",
       "}\n",
       "node {\n",
       "  name: \"Pow\"\n",
       "  op: \"Pow\"\n",
       "  input: \"x\"\n",
       "  input: \"Pow/y\"\n",
       "  attr {\n",
       "    key: \"T\"\n",
       "    value {\n",
       "      type: DT_INT32\n",
       "    }\n",
       "  }\n",
       "}\n",
       "node {\n",
       "  name: \"Identity\"\n",
       "  op: \"Identity\"\n",
       "  input: \"Pow\"\n",
       "  attr {\n",
       "    key: \"T\"\n",
       "    value {\n",
       "      type: DT_INT32\n",
       "    }\n",
       "  }\n",
       "}\n",
       "versions {\n",
       "  producer: 27\n",
       "}"
      ]
     },
     "execution_count": 126,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32.graph.as_graph_def()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.5)\n",
    "Get the concrete function's `function_def`, and look at its `signature`. This shows the names and types of the nodes in the graph that correspond to the function's inputs and outputs. This will come in handy when you deploy models to TensorFlow Serving or Google Cloud ML Engine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "name: \"__inference_cube_402009\"\n",
       "input_arg {\n",
       "  name: \"x\"\n",
       "  type: DT_INT32\n",
       "}\n",
       "output_arg {\n",
       "  name: \"identity\"\n",
       "  type: DT_INT32\n",
       "}"
      ]
     },
     "execution_count": 127,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube_func_int32.function_def.signature"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise](https://c1.staticflickr.com/9/8101/8553474140_c50cf08708_b.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 5 – Autodiff"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.1)\n",
    "Examine and run the following code examples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return 3. * x ** 2 + 2. * x - 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "metadata": {},
   "outputs": [],
   "source": [
    "def approximate_derivative(f, x, eps=1e-3):\n",
    "    return (f(x + eps) - f(x - eps)) / (2. * eps)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 130,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "7.999999999999119"
      ]
     },
     "execution_count": 130,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "approximate_derivative(f, 1.0) # true derivative = 8"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 131,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "xs = np.linspace(-2, 2, 200)\n",
    "fs = f(xs)\n",
    "x0 = 0.5\n",
    "df_x0 = approximate_derivative(f, x0)\n",
    "tangent_x0 = df_x0 * (xs - x0) + f(x0)\n",
    "plt.plot([-2, 2], [0, 0], \"k-\", linewidth=1)\n",
    "plt.plot([0, 0], [-5, 15], \"k-\", linewidth=1)\n",
    "plt.plot(xs, fs)\n",
    "plt.plot(xs, tangent_x0, \"r--\")\n",
    "plt.plot(x0, f(x0), \"ro\")\n",
    "plt.grid(True)\n",
    "plt.xlabel(\"x\", fontsize=14)\n",
    "plt.ylabel(\"f(x)\", fontsize=14, rotation=0)\n",
    "plt.axis([-2, 2, -5, 15])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 132,
   "metadata": {},
   "outputs": [],
   "source": [
    "def g(x1, x2):\n",
    "    return (x1 + 5) * (x2 ** 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {},
   "outputs": [],
   "source": [
    "def approximate_gradient(f, x1, x2, eps=1e-3):\n",
    "    df_x1 = approximate_derivative(lambda x: f(x, x2), x1, eps)\n",
    "    df_x2 = approximate_derivative(lambda x: f(x1, x), x2, eps)\n",
    "    return df_x1, df_x2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 134,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(8.999999999993236, 41.999999999994486)"
      ]
     },
     "execution_count": 134,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "approximate_gradient(g, 2.0, 3.0) # true gradient = (9, 42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<tf.Tensor: id=402035, shape=(), dtype=float32, numpy=9.0>,\n",
       " <tf.Tensor: id=402047, shape=(), dtype=float32, numpy=42.0>]"
      ]
     },
     "execution_count": 135,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x1 = tf.Variable(2.0)\n",
    "x2 = tf.Variable(3.0)\n",
    "with tf.GradientTape() as tape:\n",
    "    z = g(x1, x2)\n",
    "grads = tape.gradient(z, [x1, x2])\n",
    "grads"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GradientTape.gradient can only be called once on non-persistent tapes.\n"
     ]
    }
   ],
   "source": [
    "x1 = tf.Variable(2.0)\n",
    "x2 = tf.Variable(3.0)\n",
    "with tf.GradientTape() as tape:\n",
    "    z = g(x1, x2)\n",
    "\n",
    "dz_x1 = tape.gradient(z, x1)\n",
    "try:\n",
    "    dz_x2 = tape.gradient(z, x2)\n",
    "except RuntimeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 137,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<tf.Tensor: id=402133, shape=(), dtype=float32, numpy=9.0>,\n",
       " <tf.Tensor: id=402172, shape=(), dtype=float32, numpy=42.0>)"
      ]
     },
     "execution_count": 137,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x1 = tf.Variable(2.0)\n",
    "x2 = tf.Variable(3.0)\n",
    "with tf.GradientTape(persistent=True) as tape:\n",
    "    z = g(x1, x2)\n",
    "\n",
    "dz_x1 = tape.gradient(z, x1)\n",
    "dz_x2 = tape.gradient(z, x2)\n",
    "del tape\n",
    "dz_x1, dz_x2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 138,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[None, None]"
      ]
     },
     "execution_count": 138,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x1 = tf.constant(2.0) # <= not Variable\n",
    "x2 = tf.constant(3.0) # <= not Variable\n",
    "with tf.GradientTape() as tape:\n",
    "    z = g(x1, x2)\n",
    "\n",
    "grads = tape.gradient(z, [x1, x2])\n",
    "grads"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 139,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<tf.Tensor: id=402203, shape=(), dtype=float32, numpy=9.0>,\n",
       " <tf.Tensor: id=402215, shape=(), dtype=float32, numpy=42.0>]"
      ]
     },
     "execution_count": 139,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x1 = tf.constant(2.0)\n",
    "x2 = tf.constant(3.0)\n",
    "with tf.GradientTape() as tape:\n",
    "    tape.watch(x1)\n",
    "    tape.watch(x2)\n",
    "    z = g(x1, x2)\n",
    "\n",
    "grads = tape.gradient(z, [x1, x2])\n",
    "grads"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 140,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=402272, shape=(), dtype=float32, numpy=13.0>"
      ]
     },
     "execution_count": 140,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = tf.Variable(5.0)\n",
    "with tf.GradientTape() as tape:\n",
    "    z1 = 3 * x\n",
    "    z2 = x ** 2\n",
    "tape.gradient([z1, z2], x) # dz1_x + dz2_x = 3 + 2x = 3 + 2*5 = 13"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 141,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[None, <tf.Tensor: id=402335, shape=(), dtype=float32, numpy=6.0>],\n",
       " [<tf.Tensor: id=402389, shape=(), dtype=float32, numpy=6.0>,\n",
       "  <tf.Tensor: id=402372, shape=(), dtype=float32, numpy=14.0>]]"
      ]
     },
     "execution_count": 141,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x1 = tf.Variable(2.0)\n",
    "x2 = tf.Variable(3.0)\n",
    "with tf.GradientTape(persistent=True) as hessian_tape:\n",
    "    with tf.GradientTape() as jacobian_tape:\n",
    "        z = g(x1, x2)\n",
    "    jacobians = jacobian_tape.gradient(z, [x1, x2])\n",
    "hessians = [hessian_tape.gradient(jacobian, [x1, x2])\n",
    "            for jacobian in jacobians]\n",
    "del hessian_tape\n",
    "hessians"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2)\n",
    "Implement Gradient Descent manually to find the value of `x` that minimizes the following function `f(x)`.\n",
    "\n",
    "**Tips**:\n",
    "* Define a variable `x` and initialize it to 0.\n",
    "* Define the `learning_rate` (e.g., 0.1).\n",
    "* Write a loop that will repeatedly (1) compute the gradient of `f` (actually a derivative in this case) at the current value of `x`, and (2) tweak `x` slightly in the opposite direction (by subtracting `learning_rate * df_dx`). You can use `x.assign_sub(...)` for this.\n",
    "* Using calculus, we can find that the algorithm should converge to $x = -\\frac{1}{3}$. Indeed, the derivative of $f(x) = 3 x^2 + 2x -1$ is $f'(x) = 6x + 2$, so the minimum is reached when $f'(x) = 0$ (slope is 0), so $6x + 2 = 0$, which leads to $x = -\\frac{1}{3}$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 142,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return 3. * x ** 2 + 2. * x - 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3)\n",
    "Now use an `SGD` optimizer instead of manually tweaking `x`.\n",
    "\n",
    "**Tips**:\n",
    "* You first need to create an `SGD` optimizer, optionally specifying the learning_rate (e.g., `lr=0.1`).\n",
    "* Next replace the manual tweaking of `x` in your previous code to use `optimizer.apply_gradients()` instead. You need to pass it a list of gradient/variable pairs (just one pair in this example)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.4)\n",
    "Create a `Sequential` model for the California housing problem (no need to compile it), and train it using your own training loop, instead of using `fit()`. Evaluate your model on the validation set at the end of each epoch, and display the result.\n",
    "\n",
    "**Tips**:\n",
    "* You can use the following `random_batch()` function to get a new batch of training data at each iteration (the Data API would be much preferable, as we will see in the next notebook).\n",
    "* You can use the model like a function to make predictions: `y_pred = model(X_batch)`\n",
    "* You can use `keras.losses.mean_squared_error()` to compute the loss. Note that it returns one loss per instance, so you need to use `tf.reduce_mean()` to get the mean loss. \n",
    "* You can use `model.trainable_variables` to get the full list of trainable variables in your model.\n",
    "* You can use `zip(gradients, variables)` to create a list containing all the gradient/variable pairs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 143,
   "metadata": {},
   "outputs": [],
   "source": [
    "def random_batch(X, y, batch_size = 32):\n",
    "    idx = np.random.randint(0, len(X), size=batch_size)\n",
    "    return X[idx], y[idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.5)\n",
    "Examine and run the following code examples, then update your training loop to display the training loss at each iteration.\n",
    "\n",
    "**Tips**:\n",
    "* You can use a `keras.metrics.MeanSquaredError` instance to efficiently track the running mean squared error at each iteration.\n",
    "* Make sure you reset the metric's states at the start of each epoch.\n",
    "* You can use `print(\"\\r\", mse, end=\"\")` to display the MSE on the same line at each iteration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 144,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=402433, shape=(), dtype=float32, numpy=5.0>"
      ]
     },
     "execution_count": 144,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "metric = keras.metrics.MeanSquaredError()\n",
    "metric([5.], [2.])  # error = (2 - 5)**2 = 9\n",
    "metric([0.], [1.])  # error = (1 - 0)**2 = 1\n",
    "metric.result()     # mean error = (9 + 1) / 2 = 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 145,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=402439, shape=(), dtype=float32, numpy=0.0>"
      ]
     },
     "execution_count": 145,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "metric.reset_states()\n",
    "metric.result()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 146,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=402455, shape=(), dtype=float32, numpy=4.0>"
      ]
     },
     "execution_count": 146,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "metric([1.], [3.])  # error = (3 - 1)**2 = 4\n",
    "metric.result()     # mean error = 4 / 1 = 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Exercise solution](https://camo.githubusercontent.com/250388fde3fac9135ead9471733ee28e049f7a37/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f302f30362f46696c6f735f736567756e646f5f6c6f676f5f253238666c69707065642532392e6a7067)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 5 – Solution"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.1)\n",
    "Examine the code examples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Done"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2)\n",
    "Implement Gradient Descent manually to find the value of `x` that minimizes the following function `f(x)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 147,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return 3. * x ** 2 + 2. * x - 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 148,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=-0.3333333>"
      ]
     },
     "execution_count": 148,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "learning_rate = 0.1\n",
    "x = tf.Variable(0.0)\n",
    "\n",
    "for iteration in range(100):\n",
    "    with tf.GradientTape() as tape:\n",
    "        z = f(x)\n",
    "    dz_dx = tape.gradient(z, x)\n",
    "    x.assign_sub(learning_rate * dz_dx)\n",
    "x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3)\n",
    "Now use an `SGD` optimizer instead of manually tweaking `x`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 149,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=-0.3333333>"
      ]
     },
     "execution_count": 149,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = tf.Variable(0.0)\n",
    "optimizer = keras.optimizers.SGD(lr=0.1)\n",
    "\n",
    "for iteration in range(100):\n",
    "    with tf.GradientTape() as tape:\n",
    "        z = f(x)\n",
    "    dz_dx = tape.gradient(z, x)\n",
    "    optimizer.apply_gradients([(dz_dx, x)])\n",
    "x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.4)\n",
    "Create a `Sequential` model for the California housing problem (no need to compile it), and train it using your own training loop, instead of using `fit()`. Evaluate your model on the validation set at the end of each epoch, and display the result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 150,
   "metadata": {},
   "outputs": [],
   "source": [
    "def random_batch(X, y, batch_size = 32):\n",
    "    idx = np.random.randint(0, len(X), size=batch_size)\n",
    "    return X[idx], y[idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 151,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0 valid mse: 4.567194\n",
      "Epoch 1 valid mse: 1.0170918\n",
      "Epoch 2 valid mse: 1.7955109\n",
      "Epoch 3 valid mse: 0.596315\n",
      "Epoch 4 valid mse: 0.58101755\n",
      "Epoch 5 valid mse: 0.53072745\n",
      "Epoch 6 valid mse: 0.66618234\n",
      "Epoch 7 valid mse: 1.0245769\n",
      "Epoch 8 valid mse: 0.4872077\n",
      "Epoch 9 valid mse: 0.53296995\n"
     ]
    }
   ],
   "source": [
    "epochs = 10\n",
    "batch_size = 32\n",
    "steps_per_epoch = len(X_train) // batch_size\n",
    "optimizer = keras.optimizers.SGD()\n",
    "loss_fn = keras.losses.mean_squared_error\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1)\n",
    "])\n",
    "\n",
    "for epoch in range(epochs):\n",
    "    for step in range(steps_per_epoch):\n",
    "        X_batch, y_batch = random_batch(X_train_scaled, y_train, batch_size)\n",
    "        with tf.GradientTape() as tape:\n",
    "            y_pred = model(X_batch)\n",
    "            loss = tf.reduce_mean(loss_fn(y_batch, y_pred))\n",
    "        grads = tape.gradient(loss, model.variables)\n",
    "        grads_and_vars = zip(grads, model.variables)\n",
    "        optimizer.apply_gradients(grads_and_vars)\n",
    "    y_pred = model(X_valid_scaled)\n",
    "    valid_loss = tf.reduce_mean(loss_fn(y_valid, y_pred))\n",
    "    print(\"Epoch\", epoch, \"valid mse:\", valid_loss.numpy())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.5)\n",
    "Examine and run the following code examples, then update your training loop to display the training loss at each iteration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 152,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=639680, shape=(), dtype=float32, numpy=5.0>"
      ]
     },
     "execution_count": 152,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "metric = keras.metrics.MeanSquaredError()\n",
    "metric([5.], [2.])  # error = (2 - 5)**2 = 9\n",
    "metric([0.], [1.])  # error = (1 - 0)**2 = 1\n",
    "metric.result()     # mean error = (9 + 1) / 2 = 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 153,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=639686, shape=(), dtype=float32, numpy=0.0>"
      ]
     },
     "execution_count": 153,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "metric.reset_states()\n",
    "metric.result()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 154,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=639702, shape=(), dtype=float32, numpy=4.0>"
      ]
     },
     "execution_count": 154,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "metric([1.], [3.])  # error = (3 - 1)**2 = 4\n",
    "metric.result()     # mean error = 4 / 1 = 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 155,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0  train mse: 2.7752223\tvalid mse: 5.3992133\n",
      "Epoch 1  train mse: 0.90346783\tvalid mse: 0.85499036\n",
      "Epoch 2  train mse: 0.73343664\tvalid mse: 0.65248495\n",
      "Epoch 3  train mse: 0.6572704\tvalid mse: 0.73229337\n",
      "Epoch 4  train mse: 0.6271633\tvalid mse: 0.89375675\n",
      "Epoch 5  train mse: 0.5680934\tvalid mse: 0.62792087\n",
      "Epoch 6  train mse: 0.5731681\tvalid mse: 0.548600125\n",
      "Epoch 7  train mse: 0.5597209\tvalid mse: 0.51854014\n",
      "Epoch 8  train mse: 0.52296084\tvalid mse: 0.5335406\n",
      "Epoch 9  train mse: 0.5074372\tvalid mse: 0.5076812\n"
     ]
    }
   ],
   "source": [
    "epochs = 10\n",
    "batch_size = 32\n",
    "steps_per_epoch = len(X_train) // batch_size\n",
    "optimizer = keras.optimizers.SGD()\n",
    "loss_fn = keras.losses.mean_squared_error\n",
    "metric = keras.metrics.MeanSquaredError()  # ADDED\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Dense(30, activation=\"relu\", input_shape=X_train.shape[1:]),\n",
    "    keras.layers.Dense(1)\n",
    "])\n",
    "\n",
    "for epoch in range(epochs):\n",
    "    metric.reset_states()  # ADDED\n",
    "    for step in range(steps_per_epoch):\n",
    "        X_batch, y_batch = random_batch(X_train_scaled, y_train, batch_size)\n",
    "        with tf.GradientTape() as tape:\n",
    "            y_pred = model(X_batch)\n",
    "            loss = tf.reduce_mean(loss_fn(y_batch, y_pred))\n",
    "            metric(y_batch, y_pred)  # ADDED\n",
    "        grads = tape.gradient(loss, model.trainable_variables)\n",
    "        grads_and_vars = zip(grads, model.trainable_variables)\n",
    "        optimizer.apply_gradients(grads_and_vars)\n",
    "        print(\"\\rEpoch\", epoch, \" train mse:\", metric.result().numpy(), end=\"\")  # ADDED\n",
    "    y_pred = model(X_valid_scaled)\n",
    "    valid_loss = tf.reduce_mean(loss_fn(y_valid, y_pred))\n",
    "    print(\"\\tvalid mse:\", valid_loss.numpy())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great! You now know how to use TensorFlow's low-level API to write custom loss functions, layers, and models. You also learned how to optimize your functions by converting them to graphs: this allows TensorFlow to run operations in parallel and to perform various optimizations. Next, you learned how TensorFlow Functions and graphs are structured, and how to navigate through them. Finally, you learned how to use autodiff and write your own custom training loops."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
